(function (line) {
    /**
     * This is the primary script of webQuery framework layer.
     *
     */

    var global = this,
        identity = 1,
        memberPrefix = '__',
        BaseClass, Property, Event;

    /**
     * (Internal) Base class of all classes defined in webQuery
     * @constructor BaseClass
     */
    BaseClass = function () {
    };
    BaseClass.prototype = {
        /**
         * Execute the super method.
         */
        inherited:function () {
            var base = this.inherited.caller._base;
            if (base) {
                return base.apply(this, arguments);
            }
            else {
                console.log('Method "' + this.inherited.caller._name + '" is not an inherited method.');
                return null;
            }
        },
        /**
         * Get the specified member(event/property/method) of the class.
         * @param name
         * @param type
         * @return {Object}
         */
        getMember:function (name, type) {
            var member = this[memberPrefix + name];
            switch (type) {
                case 'event':
                    member = member instanceof Event ? member : null;
                    break;
                case 'property':
                    member = member instanceof Property ? member : null;
                    break;
                case 'method':
                    member = this[name];
                    member = line.isFunction(this[name]) ? member : null;
                    break;
            }

            if (!member) {
                console.log('object has no ' + (type || 'member') + ' "' + name + '".');
            }

            return member;
        },
        /**
         * Whether the class has specified member.
         * @param name
         * @param type
         * @return {Boolean}
         */
        hasMember:function (name, type) {
            var member = this[memberPrefix + name];
            switch (type) {
                case 'event':
                    return member instanceof Event;
                case 'property':
                    return member instanceof Property;
                case 'method':
                    return line.isFunction(this[name]);
                default:
                    return !line.isUndefined(member);
            }
        },
        /**
         * Whether the class has specified event.
         * @param name
         * @return {Boolean}
         */
        hasEvent:function (name) {
            return this.hasMember(name, 'event');
        },
        /**
         * Whether the class has specified property.
         * @param name
         * @return {Boolean}
         */
        hasProperty:function (name) {
            return this.hasMember(name, 'property');
        },
        /**
         * Whether the class has specified method.
         * @param name
         * @return {Boolean}
         */
        hasMethod:function (name) {
            return this.hasMember(name, 'method');
        },
        /**
         * Get the specified property value.
         * @param name
         * @return {*}
         */
        getProperty:function (name) {
            var prop = this.getMember(name, 'property');
            if (prop) {
                return prop.getter(this);
            }
        },
        /**
         * Set the specified property value.
         * @param name
         * @param value
         * @return {Boolean}
         */
        setProperty:function (name, value) {
            var prop = this.getMember(name, 'property');
            if (prop) {
                return prop.setter(this, value);
            }
        },
        /**
         * Shorthand method for getProperty/setProperty.
         * @param name
         * @param value
         * @return {*}
         */
        prop:function (name, value) {
            if (line.isUndefined(value)) {
                return this.getProperty(name);
            }
            else {
                this.setProperty(name, value);
            }
        },
        /**
         * Register a event handler.
         * @param name
         * @param handler
         * @param context
         */
        on:function (name, handler, context) {
            var event = this.getMember(name, 'event');
            if (event) {
                event.addHandler(this, handler, context, this);
            }
        },
        /**
         * Unregister an event handler.
         * @param name
         * @param handler
         * @param context
         */
        off:function (name, handler, context) {
            var event = this.getMember(name, 'event');
            if (event) {
                if (line.isUndefined(handler)) {
                    event.clearHandlers(this);
                }
                else {
                    event.removeHandler(this, handler, context);
                }
            }
        },
        /**
         * Fire an event
         * @param name
         * @param data
         */
        fire:function (name, data) {
            var event = this.getMember(name, 'event');
            if (event) {
                event.fireEvent(this, data);
            }
        },
        /**
         * Invoke the specified method.
         * @param name
         */
        invoke:function (name) {
            var method = this.getMember(name, 'method');
            if (method) {
                method.apply(this, Array.prototype.slice.call(arguments, 1));
            }
        }
    };

    /**
     * (Internal) Implements the Property mechanism
     * @param name
     * @param config
     * @constructor Property
     */
    Property = function (name, config) {
        line.extend(this, config);
        this.name = name;
        this.map = {};
        if (this.readonly) {
            var setter = this.setter, self = this;
            this.setter = function (owner, value) {
                if (owner.initializing) {
                    setter.call(self, owner, value);
                }
                else {
                    console.log('Cannot set readonly property "' + name + '".');
                }
            };
        }
    };
    Property.prototype = {
        constructor:Property,
        getter:function (owner) {
            return this.map[owner.$id] || this.defaultValue;
        },
        setter:function (owner, value) {
            this.map[owner.$id] = value;
        }
    };

    /**
     * (Internal) Implement the Event mechanism
     * @param name
     * @constructor Event
     */
    Event = function (name) {
        this.name = name;
        this.map = {};
    };
    Event.prototype = {
        constructor:Event,
        addHandler:function (owner, handler, context) {
            var subscribers = this.map[owner.$id] = this.map[owner.$id] || [];
            subscribers.push({
                owner:owner,
                handler:handler,
                context:context
            });
        },
        removeHandler:function (owner, handler, context) {
            var subscribers = this.map[owner.$id];
            var n = -1;
            if (subscribers) {
                line.each(subscribers, function (item, index) {
                    if (item.handler === handler && item.context === context) {
                        n = index;
                    }
                });

                if (n >= 0) {
                    subscribers.splice(n, 1);
                }
            }
        },
        clearHandlers:function (owner) {
            this.map[owner.$id] = [];
        },
        fireEvent:function (owner, data) {
            var subscribers = this.map[owner.$id];
            if (subscribers) {
                line.each(subscribers, function (item) {
                    item.handler.call(item.context, item.owner, data);
                });
            }
        }
    };

    line.extend(line, {
        /**
         * Define a namespace.
         * @param path
         * @return {Object}
         */
        namespace:function (path) {
            var tokens = path.split('.'), token;
            var target = global;

            while (token = tokens.shift()) {
                target = target[token] = target[token] || {};
            }

            return target;
        },
        /**
         * Get the specified Class
         * @param type
         * @return {Class}
         */
        getClass:function (type) {
            var tokens = type.split('.'), token;
            var target = global;

            while ((token = tokens.shift()) && target) {
                target = target[token];
            }

            if (target && target.$type) {
                return target;
            }
            else {
                console.log('Cannot find class "' + type + '".');
                return null;
            }
        },
        /**
         * Define a class.
         * @param type
         * @param base
         * @param members
         * @return {Class}
         */
        define:function (type, base, members) {
            members = members || {};
            var dotIndex = type.lastIndexOf('.');
            var ns, clsName, prototype;
            var modifier = members.modifier;
            var events = members.events || [];
            var props = members.properties || {};
            var methods = members.methods || {};
            var result;

            if (dotIndex > 0) {
                ns = line.namespace(type.slice(0, dotIndex));
                clsName = type.slice(dotIndex + 1);
            }
            else {
                ns = global;
                clsName = type;
            }

            if (base && base.$sealed) {
                throw new Error('Cannot inherit from sealed class "' + base.$type + '".');
            }

            function Class() {
                if (!Class.inheriting) {
                    if (Class.$abstract) {
                        throw new Error('Cannot initialize abstract class "' + type + '".');
                    }
                    else {
                        this.$id = identity++;
                    }
                }

                if (this.init) {
                    this.initializing = true;
                    this.init.apply(this, arguments);
                    delete this.initializing;
                }
            }

            if (line.isFunction(base)) {
                base.inheriting = true;
                prototype = new base;
                delete base.inheriting;
            }
            else {
                prototype = new BaseClass;
            }

            prototype.constructor = Class;
            prototype.$type = type;
            prototype.$base = base || null;
            Class.prototype = prototype;
            Class.$type = type;

            if (modifier) {
                Class['$' + modifier] = true;
            }

            line.forEach(events, function (value) {
                prototype[memberPrefix + value] = new Event(value);
            });

            line.each(props, function (value, key) {
                prototype[memberPrefix + key] = new Property(key, value);
            });

            line.each(methods, function (value, key) {
                value._name = key;
                if (prototype[key]) {
                    value._base = prototype[key];
                }
                prototype[key] = value;
            });

            if (Class.$static) {
                result = ns[clsName] = new Class;
            }
            else {
                result = ns[clsName] = Class;
            }

            return result;
        },
        /**
         * Extend an event member for specified class.
         * @param cls
         * @param name
         */
        extendEvent:function (cls, name) {
            cls.prototype[memberPrefix + name] = new Event();
        },
        /**
         * Extend a property member for specified class.
         * @param cls
         * @param name
         * @param config
         */
        extendProperty:function (cls, name, config) {
            cls.prototype[memberPrefix + name] = new Property(config);
        },
        /**
         * Extend a method member for specified class.
         * @param cls
         * @param name
         * @param method
         */
        extendMethod:function (cls, name, method) {
            if (line.isFunction(method)) {
                cls.prototype[memberPrefix + name] = method;
            }
        }
    });

})(line);